#include "precompiled/pchkrayserkraw.h"

#include "kraw/kraw3dmodel.h"

#include "kernel/nkernelserver.h"
#include "kernel/nfileserver2.h"
#include "kernel/nfile.h"
#include "util/nstring.h"

nArray<Kraw3DModel*> g_models;

//-----------------------------------------------------------------------------

class KrawN3D2Model : public Kraw3DModel
{
public:

    virtual bool Load( nFile* );

    enum EField
    {
        Invalid = 0,
        Type,
        NumGroups,
        NumVertices,
        NumIndices,
        NumEdges,
        VertexComps,
        NumTris,
        VertexWidth,
        Group,
        Vertex,
        Triangle,
    };

    class Tokenizer
    {
    public:
        Tokenizer( const char * );
        int GetType();
        bool Finished();
        const char *NextString();
        int NextInt();
        float NextFloat();
        EVertexFlag NextVertexFlag();

    private:
        nString current;
    };

};

//-----------------------------------------------------------------------------

Kraw3DModel* Kraw3DModel::Create( const char* path )
{
    //TODO- look for it. does it exist already?
    if ( !path ) return 0;

    Kraw3DModel* pModel(0);

    nString strName( path );
    nKernelServer* kernel = nKernelServer::Instance();
    if ( !strcmp( strName.GetExtension(), "n3d2" ) )
    {
        pModel = n_new( KrawN3D2Model );
    }
    //TODO- add other supported types

    if (!pModel) return 0;

    nFile* file = kernel->GetFileServer()->NewFileObject();
    if ( file->Open( path, "r" ) )
    {
        if ( !pModel->Load( file ) )
        {
            n_delete( pModel );
            pModel = 0;
        }
        file->Close();
    }
    file->Release();

    //TODO- save existing models in a named list
    g_models.Append( pModel );
    return pModel;
}

//-----------------------------------------------------------------------------

void Kraw3DModel::Cleanup()
{
    for ( Kraw3DModel** ppModel = g_models.Begin();
            !g_models.Empty(); g_models.Erase( ppModel ), ppModel = g_models.Begin() )
        n_delete( *ppModel );
}

//-----------------------------------------------------------------------------

Kraw3DModel::Kraw3DModel() :
    bIsValid(false),
    pVertices(0),
    pIndices(0),
    vertexFlags(0),
    nComponents(0),
    nStreams(0),
    nBatches(0),
    nVertices(0),
    nIndices(0),
    nEdges(0),
    nTriangles(0)
{
}

//-----------------------------------------------------------------------------

Kraw3DModel::~Kraw3DModel()
{
    this->Clear();
}

//-----------------------------------------------------------------------------

void Kraw3DModel::Clear()
{
    this->bIsValid = false;

    this->vertexFlags = 0;
    this->nComponents = 0;
    this->nStreams = 0;
    this->nBatches = 0;
    this->nVertices = 0;
    this->nIndices = 0;
    this->nEdges = 0;
    this->nTriangles= 0;

    if ( this->pVertices )
        n_delete_array( this->pVertices );

    if ( this->pIndices )
        n_delete_array( this->pIndices );
}

//-----------------------------------------------------------------------------

bool Kraw3DModel::IsValid()
{
    return this->bIsValid;
}

//-----------------------------------------------------------------------------

bool Kraw3DModel::GetStreamData( EVertexFlag flag, float** data )
{
    //1- find the vertex flag in question
    for ( uint iStream = 0; iStream < this->nStreams; ++iStream )
        if ( this->streams[iStream].type == flag )
        {
            //2- traverse all vertices
            *data = this->pVertices + this->streams[iStream].offset;
            return true;
        }

    return false;
}

//-----------------------------------------------------------------------------

KrawN3D2Model::Tokenizer::Tokenizer( const char *line ) :
    current(line)
{
}

int KrawN3D2Model::Tokenizer::GetType()
{
    const char* first = current.GetFirstToken( N_WHITESPACE );
    if ( first == 0 ) return Invalid;
    else if ( !strcmp( first, "type" ) )        return Type;
    else if ( !strcmp( first, "numgroups" ) )   return NumGroups;
    else if ( !strcmp( first, "numvertices" ) ) return NumVertices;
    else if ( !strcmp( first, "numindices" ) )  return NumIndices;
    else if ( !strcmp( first, "numedges" ) )    return NumEdges;
    else if ( !strcmp( first, "vertexcomps" ) ) return VertexComps;
    else if ( !strcmp( first, "numtris" ) )     return NumTris;
    else if ( !strcmp( first, "vertexwidth" ) ) return VertexWidth;
    else if ( !strcmp( first, "g" ) )           return Group;
    else if ( !strcmp( first, "v" ) )           return Vertex;
    else if ( !strcmp( first, "t" ) )           return Triangle;
    return Invalid;
}

Kraw3DModel::EVertexFlag KrawN3D2Model::Tokenizer::NextVertexFlag()
{
    const char *str = current.GetNextToken( N_WHITESPACE );
    if ( !str ) return None;
    else if ( !strcmp( str, "coord" ) )       return Coord;
    else if ( !strcmp( str, "normal" ) )      return Normal;
    else if ( !strcmp( str, "uv0" ) )         return TexCoord0;
    else if ( !strcmp( str, "uv1" ) )         return TexCoord1;
    else if ( !strcmp( str, "uv2" ) )         return TexCoord2;
    else if ( !strcmp( str, "uv3" ) )         return TexCoord3;
    else if ( !strcmp( str, "color" ) )       return Diffuse;
    else if ( !strcmp( str, "tangent" ) )     return Tangent;
    else if ( !strcmp( str, "binormal" ) )    return Binormal;
    else if ( !strcmp( str, "weights" ) )     return Weights;
    else if ( !strcmp( str, "jindices" ) )    return JIndices;
    return None;
}

const char* KrawN3D2Model::Tokenizer::NextString()
{
    return current.GetNextToken( N_WHITESPACE );
}

int KrawN3D2Model::Tokenizer::NextInt()
{
    return atoi( current.GetNextToken( N_WHITESPACE ) );
}

float KrawN3D2Model::Tokenizer::NextFloat()
{
    return (float) atof( current.GetNextToken( N_WHITESPACE ) );
}

//-----------------------------------------------------------------------------

bool KrawN3D2Model::Load( nFile* file )
{
    uint batchIndex = 0;
    bool headerDone = false;

    n_assert( file );
    n_assert( file->IsOpen() );

    //TODO- convert into filesystem-independent file reading
    //FILE* file = fopen( path, "rb" );
    //if ( !file ) return false;
    //while ( fgets( line, sizeof(line), file ) )
    //END-TODO
    char line[1024];
    while ( file->GetS( line, sizeof(line) ) )
    {
        Tokenizer tokens( line );
        const char* typeStr;
        switch ( tokens.GetType() )
        {
        case Type:
            typeStr = tokens.NextString();
            if ( !typeStr || strcmp( typeStr, "n3d2" ) ) goto Invalid;
            break;
        case NumVertices:
            this->nVertices = tokens.NextInt();
            break;
        case NumEdges:
            this->nEdges = tokens.NextInt();
            break;
        case NumGroups:
            this->nBatches = tokens.NextInt();
            this->batches.SetFixedSize( this->nBatches );
            break;
        case NumTris:
            this->nTriangles = tokens.NextInt();
            this->nIndices = this->nTriangles * 3;
            break;
        case VertexWidth:
            this->nComponents = tokens.NextInt();
            break;
        case VertexComps:
            {
                while ( EVertexFlag vertexFlag = tokens.NextVertexFlag() ) {
                    this->vertexFlags |= vertexFlag;
                    ++this->nStreams;
                }
                this->streams.SetFixedSize( nStreams );
                break;
            }
        case Group:
            {
                Batch& batch = this->batches.At(batchIndex++);
                batch.firstVertex = tokens.NextInt();
                batch.nVertices = tokens.NextInt();
                batch.firstIndex = tokens.NextInt();
                batch.nIndices = tokens.NextInt();
                batch.firstEdge = tokens.NextInt();
                batch.nEdges = tokens.NextInt();
                if ( batchIndex == this->nBatches ) headerDone = true;
                break;
            }
        default:
            goto Invalid;
        }

        if ( headerDone ) break;
    }

    //allocate all streams
    uint iStream = 0;
    uint curOffset = 0;
    for ( uint index = 0; index < NumVertexFlags; ++index )
    {
        if ( this->vertexFlags & (1<<index) )
        {
            Stream& stream = this->streams[iStream++];
            stream.type = EVertexFlag(1<<index);
            switch ( stream.type )
            {
                case Coord: stream.size = 3; break;
                case Normal: stream.size = 3; break;
                case Tangent: stream.size = 3; break;
                case Binormal: stream.size = 3; break;
                case Diffuse: stream.size = 4; break;
                case Specular: stream.size = 4; break;
                case TexCoord0: stream.size = 2; break;
                case TexCoord1: stream.size = 2; break;
                case TexCoord2: stream.size = 2; break;
                case TexCoord3: stream.size = 2; break;
                case Weights: stream.size = 4; break;
                case JIndices: stream.size = 4; break;
            }
            stream.offset = curOffset;
            curOffset += stream.size;
        }
    }
    //check that vertex width matches the one in the file
    if ( curOffset != this->nComponents ) return false;

    //allocate enough space for the vertex and index data
    this->pVertices = n_new_array( float, this->nVertices * this->nComponents );
    this->pIndices = n_new_array( uint, this->nIndices );

    int curVertex = 0, curIndex = 0;

    //TODO- convert into filesystem independent file reading
    //while ( fgets( line, sizeof(line), file ) )
    //END-TODO
    while ( file->GetS( line, sizeof(line) ) )
    {
        Tokenizer tokens( line );
        switch ( tokens.GetType() )
        {
        case Vertex:
            for ( uint index = 0; index < this->nComponents; ++index )
                this->pVertices[curVertex++] = tokens.NextFloat();
            break;
        case Triangle:
            for ( uint index = 0; index < 3; ++index )
                this->pIndices[curIndex++] = tokens.NextInt();
            break;
        default:
            goto Invalid;
        }
    }

    //TODO- fill face attributes

    bIsValid = true;
    return true;

Invalid:
    return false;
}
